package com.wolfking.converter.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.*;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.wolfking.converter.transform.FieldTransform;
import com.wolfking.converter.transform.base.BooleanFieldTransform;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.FastDateFormat;

import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.time.*;
import java.util.*;

@Slf4j
public class JsonConverterUtil {

    public static final Gson gson = new GsonBuilder().setPrettyPrinting()
            .setDateFormat("yyyy-MM-dd HH:mm:ss")
            .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).registerTypeAdapter(LocalDateTime.class, new LocalDateTimeDeserializer())
            .registerTypeAdapter(LocalDate.class, new LocalDateSerializer()).registerTypeAdapter(LocalDate.class, new LocalDateDeserializer())
            .registerTypeAdapter(byte[].class, new ByteArraySerializer()).registerTypeAdapter(byte[].class, new ByteArrayDeserializer())
            .registerTypeAdapter(Byte[].class, new ByteArraySerializer()).registerTypeAdapter(byte[].class, new ByteArrayDeserializer())
            .registerTypeAdapterFactory(MapTypeAdapter.FACTORY).create();

    public static final FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

    public static final FastDateFormat formatDate = FastDateFormat.getInstance("yyyy-MM-dd");

    private static class LocalDateSerializer implements JsonSerializer<LocalDate> {
        @Override
        public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {

            ZoneId zoneId = ZoneId.systemDefault();
            ZonedDateTime zonedDateTime = LocalDateTime.of(src, LocalTime.now()).atZone(zoneId);
            Instant instant = zonedDateTime.toInstant();
            Date from = Date.from(instant);
            return new JsonPrimitive(formatDate.format(from));
        }
    }

    private static class LocalDateDeserializer implements JsonDeserializer<LocalDate> {
        @Override
        public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            try {
                String asString = json.getAsJsonPrimitive().getAsString();
                Date parse = formatDate.parse(asString);
                Instant instant = parse.toInstant();
                // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
                return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate();
            } catch (Exception e) {
                throw new JsonParseException(e);
            }
        }
    }

    private static class BooleanDeserializer implements JsonDeserializer<Boolean> {
        @Override
        public Boolean deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
            try {
                String asString = json.getAsJsonPrimitive().getAsString();
                return new BooleanFieldTransform().transform(asString);
            } catch (Exception e) {
                throw new JsonParseException(e);
            }
        }
    }


    private static class ByteArraySerializer implements JsonSerializer<byte[]> {
        @Override
        public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(new String(src, StandardCharsets.UTF_8));
        }
    }

    private static class ByteArrayDeserializer implements JsonDeserializer<byte[]> {
        @Override
        public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            return json.getAsJsonPrimitive().getAsString().getBytes(StandardCharsets.UTF_8);
        }
    }


    private static class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
        @Override
        public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
            ZoneId zoneId = ZoneId.systemDefault();
            ZonedDateTime zonedDateTime = src.atZone(zoneId);
            Instant instant = zonedDateTime.toInstant();
            Date from = Date.from(instant);
            return new JsonPrimitive(format.format(from));
        }
    }

    private static class LocalDateTimeDeserializer implements JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            try {
                String asString = json.getAsJsonPrimitive().getAsString();
                Date parse = format.parse(asString);
                Instant instant = parse.toInstant();
                // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
                return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
            } catch (Exception e) {
                throw new JsonParseException(e);
            }
        }
    }

    private static class MapTypeAdapter extends TypeAdapter<Object> {
        public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
            @Override
            @SuppressWarnings("unchecked")
            public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
                if (type.getRawType() == Object.class) {
                    return (TypeAdapter<T>) new MapTypeAdapter(gson);
                }
                return null;
            }
        };

        private final Gson gson;

        private MapTypeAdapter(Gson gson) {
            this.gson = gson;
        }

        @Override
        public Object read(JsonReader in) throws IOException {
            JsonToken token = in.peek();
            //判断字符串的实际类型
            switch (token) {
                case BEGIN_ARRAY:
                    List<Object> list = new ArrayList<>();
                    in.beginArray();
                    while (in.hasNext()) {
                        list.add(read(in));
                    }
                    in.endArray();
                    return list;

                case BEGIN_OBJECT:
                    Map<String, Object> map = new LinkedTreeMap<>();
                    in.beginObject();
                    while (in.hasNext()) {
                        map.put(in.nextName(), read(in));
                    }
                    in.endObject();
                    return map;
                case STRING:
                    return in.nextString();
                case NUMBER:
                    String s = in.nextString();
                    if (s.contains(".")) {
                        return Double.valueOf(s);
                    } else {
                        try {
                            return Integer.valueOf(s);
                        } catch (Exception e) {
                            return Long.valueOf(s);
                        }
                    }
                case BOOLEAN:
                    return in.nextBoolean();
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    throw new IllegalStateException();
            }
        }

        @Override
        public void write(JsonWriter out, Object value) throws IOException {
            if (value == null) {
                out.nullValue();
                return;
            }
            //noinspection unchecked
            TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
            if (typeAdapter instanceof MapTypeAdapter) {
                out.beginObject();
                out.endObject();
                return;
            }
            typeAdapter.write(out, value);
        }
    }

    public static String toJson(Object obj) {
        return gson.toJson(obj);
    }

    public static Map<String, Object> toMap(Object obj) {
        String s = obj.toString();
        return gson.fromJson(s, new TypeToken<TreeMap<String, Object>>() {
        }.getType());
    }

    public static List<Map<String, Object>> toListMap(JsonArray array) {
        String s = array.toString();
        return gson.fromJson(s, new TypeToken<List<TreeMap<String, Object>>>() {
        }.getType());
    }


    public static <T> T assemblyObject(Map<String, Object> map, Class<T> clazz) {
        return assemblyObject(map, clazz, Maps.newHashMap());
    }

    public static <T> T assemblyObject(Map<String, Object> map, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap) {
        return gson.fromJson(assemblyObject(map, fieldTransformMap), clazz);
    }

    public static <T> List<T> assemblyObjectList(List<Map<String, Object>> mapList, Class<T> clazz) {
        return assemblyObjectList(mapList, clazz, Maps.newHashMap());
    }

    public static <T> List<T> assemblyObjectList(List<Map<String, Object>> mapList, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap) {
        List<T> list = Lists.newArrayList();
        for (Map<String, Object> map : mapList) {
            list.add(gson.fromJson(assemblyObject(map, fieldTransformMap), clazz));
        }
        return list;
    }

    public static String assemblyJsonString(Map<String, Object> map) {
        return gson.toJson(assemblyObject(map));
    }

    public static JsonObject assemblyObject(Map<String, Object> map) {
        return assemblyObject(map, Maps.newHashMap());
    }

    public static JsonObject assemblyObject(Map<String, Object> map, Map<String, FieldTransform<?>> fieldTransformMap) {
        JsonObject jsonObject = new JsonObject();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
            setProperty(jsonObject, fieldName, value, fieldTransformMap.get(fieldName));
        }
        return jsonObject;
    }

    public static JsonArray assemblyObjectArray(List<Map<String, Object>> mapList) {
        return assemblyObjectArray(mapList, Maps.newHashMap());
    }

    public static JsonArray assemblyObjectArray(List<Map<String, Object>> mapList, Map<String, FieldTransform<?>> fieldTransformMap) {
        JsonArray array = new JsonArray();
        for (Map<String, Object> map : mapList) {
            array.add(assemblyObject(map, fieldTransformMap));
        }
        return array;
    }


    private static void setProperty(JsonObject object, String path, Object value, FieldTransform<?> fieldTransformCustom) {
        if (!path.contains(".")) {
            if (value == null) {
                object.add(path, JsonNull.INSTANCE);
                return;
            }
            if (fieldTransformCustom != null) {
                try {
                    object.add(path, gson.toJsonTree(fieldTransformCustom.transform(value)));
                } catch (Exception e) {
                    object.add(path, gson.toJsonTree(value));
                }
            } else {
                object.add(path, gson.toJsonTree(value));
            }
        } else {
            String[] split = path.split("\\.");
            path = split[0];
            JsonObject newObj = object.getAsJsonObject(path);
            if (newObj == null) {
                newObj = new JsonObject();
                object.add(path, newObj);
            }
            path = Arrays.stream(split).skip(1).reduce("", (e1, e2) -> (e1 + "." + e2)).substring(1);
            setProperty(newObj, path, value, fieldTransformCustom);
        }
    }
}
